#include "cfg_csem.h"
#include "cfg_v4.h"
#include "common.h"
#include "csem_impl.h"
#include "dist.h"
#include "sleep_api.h"
#include "vector.h"

// placeholder
#define N1_MOTION_THRESHOLD 0

static sleep_types_t _get_staging_type(const dist_action_t *dist,
                                       sleep_types_t last_type,
                                       const smp_hr_t *temp_list, size_t count,
                                       uint32_t *out_interval) {
  if (ENUM_SLEEP_STAGING_TYPE_NONE == last_type) {
    return ENUM_SLEEP_STAGING_TYPE_NREM1;
  }

  const smp_hr_t *tail = temp_list + count - 1;
  *out_interval = (tail->ts - temp_list->ts) / 60000;

  int32_t steps_diff = (int32_t)tail->steps - (int32_t)temp_list->steps;

  uint32_t sum_mot = 0;
  uint8_t all = 1;

  for (size_t i = 0; i < count; i++) {
    const smp_hr_t *cur = temp_list + i;
    if (cur->motion > DEF_EVAL(dist, N3_MOTION_THRESHOLD))
      all = 0;
    sum_mot += cur->motion;
  }

  int32_t hr_diff = (int32_t)tail->rate - (int32_t)temp_list->rate;
  int32_t hrv_diff = (int32_t)tail->hrv - (int32_t)temp_list->hrv;

  if (all)
    return hr_diff <= N3_HR_DIFF_THRESHOLD && hrv_diff <= N3_HRV_DIFF_THRESHOLD
               ? ENUM_SLEEP_STAGING_TYPE_NREM3
               : ENUM_SLEEP_STAGING_TYPE_NREM1;

  if (steps_diff <= N1_STEPS_DIFF_THRESHOLD &&
      sum_mot <= DEF_EVAL(dist, N1_MOTION_THRESHOLD)()) {
    return (hr_diff >= REM_HR_DIFF_THRESHOLD &&
            hrv_diff >= REM_HRV_DIFF_THRESHOLD &&
            sum_mot >= REM_MIN_MOTION_THRESHOLD)
               ? ENUM_SLEEP_STAGING_TYPE_REM
               : ENUM_SLEEP_STAGING_TYPE_NREM1;
  }

  if (steps_diff <= REM_STEPS_DIFF_THRESHOLD &&
      sum_mot <= REM_MAX_MOTION_THRESHOLD)
    return ENUM_SLEEP_STAGING_TYPE_REM;

  return ENUM_SLEEP_STAGING_TYPE_WAKE;
}

static void _append_staging_list(uint64_t sleep, uint64_t wake,
                                 sleep_types_t cur_type,
                                 sleep_types_t last_type,
                                 const smp_hr_t *temp_list, size_t count,
                                 linked_list_t *staging_list) {
  if (ENUM_SLEEP_STAGING_TYPE_NONE == last_type) {
    each_activity_t *s = (each_activity_t *)malloc(sizeof(each_activity_t));

    s->begin = sleep;
    s->end = (temp_list + count - 1)->ts;
    s->type = cur_type;

    append_item(staging_list, s, NULL);
    return;
  }

  if (count <= 1) {
    each_activity_t *tail = index_item(staging_list, -1, NULL);

    if (tail->end < wake) {
      if (ENUM_SLEEP_STAGING_TYPE_NREM1 == tail->type ||
          ENUM_SLEEP_STAGING_TYPE_WAKE == tail->type) {
        tail->end = wake;
      } else {
        tail->end = wake;
        tail->type = ENUM_SLEEP_STAGING_TYPE_NREM1;
      }
    }

    return;
  }

  if (cur_type == last_type) {
    each_activity_t *tail = index_item(staging_list, -1, NULL);

    tail->end = (temp_list + count - 1)->ts;
    tail->type = cur_type;
  } else {
    each_activity_t *s = (each_activity_t *)malloc(sizeof(each_activity_t));

    s->begin =
        ((const each_activity_t *)index_item(staging_list, -1, NULL))->end;
    s->end = (temp_list + count - 1)->ts;
    s->type = cur_type;

    append_item(staging_list, s, NULL);
  }
}

void csem_seg(const dist_action_t *dist, uint64_t sleep, uint64_t wake,
              const smp_hr_t *hr_list, size_t hl, linked_list_t *ll_seg,
              uint32_t *csem_inteval) {
#define WINDOW_SIZE 2

  if (!hl) {
    return;
  }

  if (wake <= sleep + DEF_EVAL(dist, MIN_STAGING_TIME)) {
    each_activity_t *s = malloc(sizeof(*s));
    s->begin = sleep;
    s->end = wake;
    s->type = ENUM_SLEEP_STAGING_TYPE_NREM1;

    append_item(ll_seg, s, NULL);
    return;
  }

  if (hl < WINDOW_SIZE)
    return;

  const smp_hr_t *window_list = NULL;
  sleep_types_t last_type = ENUM_SLEEP_STAGING_TYPE_NONE;
  for (size_t i = 0; i < hl; i++) {
    if (hl - i < WINDOW_SIZE) {
      break;
    }

    window_list = hr_list + i;
    sleep_types_t cur_type = _get_staging_type(dist, last_type, window_list,
                                               WINDOW_SIZE, csem_inteval);
    _append_staging_list(sleep, wake, cur_type, last_type, window_list,
                         WINDOW_SIZE, ll_seg);
    last_type = cur_type;
  }

  if (hl >= WINDOW_SIZE) {
    // 至少能滑1次
    _append_staging_list(sleep, wake, ENUM_SLEEP_STAGING_TYPE_NREM1, last_type,
                         hr_list + hl - 1, WINDOW_SIZE - 1, ll_seg);
  }
}